Shell学习笔记15-Shell脚本的调试

GO


这篇笔记是关于Shell脚本的调试知识,掌握了Shell脚本的调试技巧,可以让我们在开发大型脚本时做到事半功倍。虽然掌握了Shell脚本的调试技巧很重要,但是如果能够掌握并养成良好的Shell脚本开发的规范和习惯,就可以从源头上降低开发脚本的错误率,从而降低脚本调试的难度和时间,达到未雨绸缪的效果。

1. 常见Shell脚本错误范例

1.1. if条件语句缺少结尾关键字

  • 范例代码:

    1
    2
    3
    4
    5
    [root@theshu ~]# cat if.sh
    #!/bin/bash
    if [ 10 -lt 12 ]
    then
    echo "Yes, 10 is less than 12."
  • 执行结果如下:

    1
    2
    [root@theshu ~]# sh if.sh
    if.sh: line 5: syntax error: unexpected end of file

结果给出了提示,第5行存在语法错误:这不是所期待的(意外的)文件结尾。根据这个提示,我们知道脚本的尾部有问题,仔细观察发现,原来是缺少了fi结尾。

说明;

  • 在Shell脚本开发中,脚本缺少fi关键字是很常见的问题。另外,当执行脚本时提示输出错误后,不要只看那些提示的错误行,而是要观察整个相关的代码段。
  • Shell脚本解释器一般不会对脚本错误进行精确的定位,而是在试图结束一个语句时进行错误统计,因此,掌握语法并养成良好的规范和习惯就显得很重要。

1.2. 循环语句缺少关键字

for、while、until和case语句中的错误是指实际语句段不正确,也许是漏写或拼错了固定结构中的一个保留字。

范例:循环结构语句中缺少关键字引起的错误。

  • 脚本内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #!/bin/bash
    while true
    do
    status=`curl -I -s --connect-timeout 10 $1 | awk '{print $2}'`
    ok=`curl -I -s --connect-timeout 10 | head -1 | cut -d " " -f 3`
    if [ "$status" = "200" ] && [ "$ok" = "ok" ]; the
    echo "this yrl is good"
    else
    echo "this url is bad"
    fi
    sleep 3
    done
  • 执行上述脚本的结果:

    1
    2
    while.sh: line 8: syntax error near unexpected token `else'
    while.sh: line 8: ` else'
  • 通过提示可知吗,是第8行的语法错误,在else附近。经过前后观察可以发现,the少加了个n,应为then

  • 如果报错的是循环或条件独立的语句,对上下关联部分语句也都要看一下。

1.3. 成对的符号落了单

成对的符号有[](){}""''等,如果它们落了单,也会导致一些错误。但是這些错误通过错误提示即可很快排除。

1.4. 中括号两端没空格

中括号两端没空格导致的错误

  • 代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #!/bin/bash
    a=3
    b=1
    if [$a -lt $b]
    then
    echo "Yes,$a < $b"
    else
    echo "Yes,$a >= $b"
    fi
  • 执行结果如下:

    1
    2
    zhongkuohao.sh: line 4: [3: command not found
    Yes,3 >= 1

1.5. Shell语法调试小结

Shell的语法调试并不是很智能,报错也不是很精准,因此就需要我们在开发规范和书写脚本上多下功夫,企业里的Shell脚本大多都是比较短的,因此,开发起来也相对轻松。

如果能在开发过程中,重视书写习惯、开发规范和开发制度,那么就会减少脚本调试的难度和次数,提升开发效率。此外,要对Shell的基本语法十分熟练,这样才能更好地利用脚本调试。

此外,写脚本的思路要清晰,否则将给调试带来困难。可采用的思路如下:

  • 首先思考开发框架,尽量模块化开发,复杂的脚本要简单化、分段实现。并采用打游戏过关的思想(第一关、第二关、第三关、直到通关)去完善框架结构。
  • 然后利用函数分模块开发,语法结构如下:

    1
    2
    3
    4
    函数1()
    函数2()
    main()
    main $* #<==执行主函数
  • 需要注意的是,不要强制模块化,分块要合理。

2. Shell脚本调试技巧

2.1. 使用dos2unix命令处理在Windows下开发的脚本

因为Windows系统下的文本文件和Linux系统下的文本文件在一些符号上面有差别(比如结尾换行符),所以,在Windows下编辑的Shell脚本直接放在Linux下执行会出现错误,所以需要用dos2unix工具来转换一下文件。

格式:dos2unix filename.sh

如果没有dos2unix,则用下面的命令进行安装:yum install dos2unix -y

2.2. 使用echo命令调试

echo命令是最有用的调试脚本的工具之一。一般应在可能出现问题的脚本的重要部分加入echo命令,例如在变量读取或修改操作的前后加入echo命令,并紧挨着退出命令exit。

利用echo调试一个简单的判断脚本:

1
2
3
4
5
6
7
#!/bin/bash
read -p "Pls input two num:" a b
echo $a $b #<==增加打印输出,确认变量值是否符合要求
exit #<==退出脚本,目的是不执行后面的代码
###########
后面的代码省略...
...

提示:这个调试方法不是Shell的专利,PHP、ASP、Perl、Python等语言都可以使用这样简单又好用的调试方法。

2.3. 使用bash命令参数调试

可以利用bashsh本身自带的参数进行调试。格式为:sh [-nvx] script.sh

参数 说明
-n 不会执行该脚本,仅查询脚本语法是否有问题,并给出错误提示
-v 在执行脚本时,先将脚本的内容输出到屏幕上,然后执行脚本,如果有错误,也会给出错误提示
-x 将执行的脚本内容及输出显示到屏幕上,这是对调试很有用的参数

说明:这些参数同样适用于bash

2.3.1. sh参数-n的测试

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@theshu ~]# cat script.sh
#!/bin/bash
echo "Hello $USER,"
echo "Today is $(date +%F)"
[root@theshu ~]# sh -n script.sh #<==当脚本没错时什么也不显示
[root@theshu ~]# vim script.sh
[root@theshu ~]# cat script.sh #<==修改成有问题的脚本
#!/bin/bash
echo "Hello $USER,
echo "Today is $(date +%F)"
[root@theshu ~]# sh -n script.sh #<==有错误显示
script.sh: line 3: unexpected EOF while looking for matching `"'
script.sh: line 4: syntax error: unexpected end of file

2.3.2. sh参数-v的测试

  • 普通的错误脚本的执行结果如下:

    1
    2
    3
    4
    5
    6
    [root@theshu ~]# sh -v script.sh
    #!/bin/bash
    echo "Hello $USER,
    echo "Today is $(date +%F)"
    script.sh: line 3: unexpected EOF while looking for matching `"'
    script.sh: line 4: syntax error: unexpected end of file
  • 带函数的错误脚本的执行结果如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    [root@theshu ~]# sh -v c.sh
    #!/bin/bash
    theshu(){
    echo "I am theshu!"
    }
    theshan(){
    echo "I am theshan "
    }
    theshu1
    c.sh: line 11: theshu1: command not found
    theshan
    I am theshan

2.3.3. sh参数-x的测试

  • 跟踪script.sh脚本的执行过程:

    1
    2
    3
    4
    [root@theshu ~]# cat script.sh
    #!/bin/bash
    echo "Hello $USER,"
    echo "Today is $(date +%F)"
  • 执行结果如下:

    1
    2
    3
    4
    5
    6
    [root@theshu ~]# sh -x script.sh
    + echo 'Hello root,'
    Hello root,
    ++ date +%F
    + echo 'Today is 2018-03-01'
    Today is 2018-03-01

说明:

  • 使用-x追踪脚本是一种非常好的方法,它可以在执行前列出所执行的所有程序段
  • 如果是程序段落,则在输出时,最前面会加上+符号,表示它是程序代码
  • 一般情况下如果是调试逻辑错误的脚本,用-x的效果更佳
  • 缺点是:加载系统函数库等很多我们不想看其整个过程的脚本时,会有太多输出,导致很难查看所需的内容
  • 利用sh -x filename.sh调试的缺点可以用set -x命令来弥补,它可以缩小调试的作用域
  • 在一个比较长的脚本中,你会看到很多的执行跟踪的输出,有时候阅读起来非常费劲,此时,可以在每一行的前面内容修改一下提示符,这会非常有用。要做到这样,只需要设置下面的环境变量:

    1
    2
    3
    4
    5
    # set | grep PS[1-5]
    PS1='[\u@\h \W]\$ '
    PS2='> '
    PS4='+ '
    #<==只需修改PS4变量即可,在默认情况下表示加号
  • 参数-x是一个不可多得的参数,在生产环境中,经常会通过参数-x来实现调试的目的。一般情况下,如果执行脚本发生问题(非语法问题),利用-x参数,就可以知道问题出在哪一行。

2.4. 使用set命令调试部分脚本内容

set命令也可以用于辅助脚本调试。以下是set命令常用的调试选项。

选项 说明
set -n 读命令但并不执行
set -v 显示读取的所有行
set -x 显示所有命令及其参数

提示:通过set -x命令开启调试功能,而通过set +x关闭调试功能。

set命令的最大优点是,和bash -x相比,set -x可以缩小调试的作用域。

范例:调试打印九九乘法表的简版脚本:

  • 脚本内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [root@theshu ~]# cat 99dug.sh
    #!/bin/bash
    set -x #<==表示从这里开启脚本调试
    for a in `seq 9`
    do
    for b in `seq 9`
    do
    [ $a -ge $b ] && echo -en "$a x $b = $(expr $a \* $b) "
    done
    set +x #<==表示到这里结束脚本调试
    echo " "
    done
  • 执行脚本查看调试输出结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    [root@theshu ~]# sh 99dug.sh
    ++ seq 9
    + for a in '`seq 9`'
    ++ seq 9
    + for b in '`seq 9`'
    + '[' 1 -ge 1 ']'
    ++ expr 1 '*' 1
    + echo -en '1 x 1 = 1 '
    1 x 1 = 1 + for b in '`seq 9`'
    + '[' 1 -ge 2 ']'
    + for b in '`seq 9`'
    + '[' 1 -ge 3 ']'
    + for b in '`seq 9`'
    + '[' 1 -ge 4 ']'
    + for b in '`seq 9`'
    + '[' 1 -ge 5 ']'
    + for b in '`seq 9`'
    + '[' 1 -ge 6 ']'
    + for b in '`seq 9`'
    + '[' 1 -ge 7 ']'
    + for b in '`seq 9`'
    + '[' 1 -ge 8 ']'
    + for b in '`seq 9`'
    + '[' 1 -ge 9 ']'
    + set +x
    2 x 1 = 2 2 x 2 = 4
    3 x 1 = 3 3 x 2 = 6 3 x 3 = 9
    4 x 1 = 4 4 x 2 = 8 4 x 3 = 12 4 x 4 = 16
    5 x 1 = 5 5 x 2 = 10 5 x 3 = 15 5 x 4 = 20 5 x 5 = 25
    6 x 1 = 6 6 x 2 = 12 6 x 3 = 18 6 x 4 = 24 6 x 5 = 30 6 x 6 = 36
    7 x 1 = 7 7 x 2 = 14 7 x 3 = 21 7 x 4 = 28 7 x 5 = 35 7 x 6 = 42 7 x 7 = 49
    8 x 1 = 8 8 x 2 = 16 8 x 3 = 24 8 x 4 = 32 8 x 5 = 40 8 x 6 = 48 8 x 7 = 56 8 x 8 = 64
    9 x 1 = 9 9 x 2 = 18 9 x 3 = 27 9 x 4 = 36 9 x 5 = 45 9 x 6 = 54 9 x 7 = 63 9 x 8 = 72 9 x 9 = 81

提示:加了set -x,在运行脚本的时候,就不要使用sh -x了。

熟悉sh及set的用法,可以让我们能够得心应手地管理Linux下Shell脚本执行的过程。在Shell脚本的学习上,需要“多看、多模仿,并将已有脚本修改成自己需要的样式”,这是最快的学习手段。网络上有很多实用的脚本,作为初学者,可以先将这些脚本拿过来,将其改成为适合自己服务器的脚本,然后再慢慢熟悉,慢慢尝试开发新的脚本。这种先模仿后开发的学习方法,会让你的学习事半功倍,而且学习起来也会很有兴趣和成就感。

Linux系统上本来就有很多适合阅读的规范脚本,如果想要更加深入地掌握Shell脚本编程,最好的方法就是阅读系统的这些脚本,然后仔细研究每一行都是什么作用,为什么这样写,久而久之,你的Shell开发水平就会得到大幅度提高。

2.5. 其它调试Shell脚本的工具

下面再引荐两款Shell的调试工具吗,可以作为扩展知识来研究一下。

  1. Shell调试工具:bashdb:它是一个类似于GDB的调试工具,可以完成对Shell脚本的断点设置、单步执行、变量观察等许多功能。不过一般很少有人使用它。
  2. Shell调试工具:shellcheck:它是一个可检查sh/bash脚本和命令语法的小工具。一般也是很少有人使用它。

3. 小结

这篇学习笔记主要介绍了Shell的调试技巧,包括:

  • 要记得首先用dos2unix对脚本(从其它地方拿来用的)进行格式化。
  • 执行脚本根据报错来调试时,要知道有时所报错误会不准确,应多关联上下文查看。
  • 可通过sh -x命令调试整个脚本,且显示执行过程。
  • set -xset +x命令用于调试部分脚本的执行过程(可在脚本中设置)。
  • 可通过echo命令输出脚本中要确认的变量及相关内容,然后紧跟着使用exit退出,不执行后面程序,这种方式便于一步步跟踪脚本,对于逻辑错误的调试比较好用。写法即:echo $var; exit
  • 最关键的还是要语法熟练,养成良好的编码习惯,提高编程思想,将错误扼杀在萌芽状态之中,从而降低错误率,减轻调试的负担,提高开发效率。

OK

0%